//
//  CIMService.m
//  Pods
//
//  Created by Chentao on 2022/9/17.
//
//

#import "CIMService.h"
#import "CIMSignalService.h"
#import "SentBody.pbobjc.h"
#import "ReplyBody.pbobjc.h"
#import "Message.pbobjc.h"
#import <UIKit/UIKit.h>
#import "CIMWeakProxy.h"
#import "CIMKeychain.h"

#define ServiceObserversEnumerateBegin                                    \
    for (NSValue *value in self.serviceObservers) {                       \
        id<CIMServiceObserver> observer = [value nonretainedObjectValue]; \
        dispatch_async(dispatch_get_main_queue(), ^{                      \

#define ServiceObserversEnumerateEnd \
        });                          \
    }                                \

NSString *const DEVICEID_KEY = @"deviceId";

@interface CIMService ()<CIMSignalServiceDelegate>

@property (nonatomic, strong) CIMSignalService *signalService;

@property (nonatomic, strong) NSMutableArray<CIMServiceObserver> *serviceObservers;

@property (nonatomic, assign) BOOL manualDisconnect;

@property (nonatomic, assign) NSInteger reconnectionCount;

@property (nonatomic, strong) NSTimer *reconnectionTimer;

@end

@implementation CIMService{
    
    dispatch_queue_t imservice_queue;
    
}

static CIMService *SINGLETON = nil;

static bool isFirstAccess = YES;

#pragma mark - Public Method

+ (id)sharedInstance {
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        isFirstAccess = NO;
        SINGLETON = [[super allocWithZone:NULL] init];
    });

    return SINGLETON;
}

#pragma mark - Life Cycle

+ (id)allocWithZone:(NSZone *)zone {
    return [self sharedInstance];
}

- (id)copy {
    return [[CIMService alloc] init];
}

- (id)mutableCopy {
    return [[CIMService alloc] init];
}

- (id)init {
    if (SINGLETON) {
        return SINGLETON;
    }

    if (isFirstAccess) {
        [self doesNotRecognizeSelector:_cmd];
    }

    self = [super init];

    self.reconnection = YES;
    self.reconnectionTimeInterval = 1.0;
    self.reconnectionMaxCount = 3;
    self.reconnectionCount = 0;

    imservice_queue = dispatch_queue_create("CIMServiceQueue", DISPATCH_QUEUE_SERIAL);
    return self;
}

#pragma mark - serviceObservers
- (NSMutableArray<CIMServiceObserver> *)serviceObservers {
    if (!_serviceObservers) {
        _serviceObservers = [[NSMutableArray<CIMServiceObserver> alloc] init];
    }
    return _serviceObservers;
}

- (void)addServiceObserver:(id<CIMServiceObserver>)observer {
    dispatch_async(imservice_queue, ^{
        NSValue *value = [NSValue valueWithNonretainedObject:observer];

        if (![self.serviceObservers containsObject:value]) {
            [self.serviceObservers addObject:value];
        }
    });
    
}

- (void)removeServiceObserver:(id<CIMServiceObserver>)observer {
    dispatch_async(imservice_queue, ^{
        NSValue *value = [NSValue valueWithNonretainedObject:observer];

        if ([self.serviceObservers containsObject:value]) {
            [self.serviceObservers removeObject:value];
        }
    });
}

#pragma mark -

- (BOOL)isConnected {
    return self.signalService.isConnected;
}

- (void)configHost:(NSString *)host onPort:(NSInteger)port {
    dispatch_async(imservice_queue, ^{
        CIMSignalServiceConfig *config = [[CIMSignalServiceConfig alloc] init];
        config.host = host;
        config.port = port;
        self.signalService.serviceConfig = config;
    });
}

- (void)connection {
    dispatch_async(imservice_queue, ^{
        if (self.signalService.isConnected) {
            return;
        }
        [self.signalService connect];
    });
}

- (void)bindUserId:(NSString *)userId {
    dispatch_async(imservice_queue, ^{
        if (self.signalService.isConnected) {
            CIMSignalPacket *clientBindPacket = [self createClientBindPacketWithUserId:userId];
            [self.signalService sendSignalPacket:clientBindPacket];
        }
    });
}

- (void)sendRequest:(CIMSentBody *)sendBody {
    dispatch_async(imservice_queue, ^{
        SentBodyModel *sentBodyModel = [[SentBodyModel alloc] init];

        sentBodyModel.key = sendBody.key;
        sentBodyModel.timestamp = sendBody.timestamp;
        sentBodyModel.data_p = [[NSMutableDictionary alloc] initWithDictionary:sendBody.data];

        NSData *bodyData = sentBodyModel.data;

        CIMSignalPacket *packet = [[CIMSignalPacket alloc] init];
        packet.tag = CIMSignalPacketTypeSentBody;
        packet.bodyLength = bodyData.length;
        packet.packetBody = bodyData;
        [self.signalService sendSignalPacket:packet];
    });
}

- (void)disconnect {
    dispatch_async(imservice_queue, ^{
        if (self.signalService.isConnected) {
            self.manualDisconnect = YES;
            [self stopReconnectionTimer];
            [self.signalService close];
        }
    });
}

#pragma mark - signalService

- (CIMSignalService *)signalService {
    if (!_signalService) {
        _signalService = [[CIMSignalService alloc] init];
        _signalService.delegate = self;
    }
    return _signalService;
}

#pragma mark - CIMSignalServiceDelegate

- (void)signalServiceWillConnect:(CIMSignalService *)signalService {
    dispatch_async(imservice_queue, ^{
        ServiceObserversEnumerateBegin
        if ([observer respondsToSelector:@selector(serviceWillConnect:)]) {
            [observer serviceWillConnect:self];
        }
        ServiceObserversEnumerateEnd
    });
}

- (void)signalServiceWillConnect:(CIMSignalService *)signalService error:(NSError *)error {
    dispatch_async(imservice_queue, ^{
        ServiceObserversEnumerateBegin
        if ([observer respondsToSelector:@selector(serviceWillConnect:error:)]) {
            [observer serviceWillConnect:self error:error];
        }
        ServiceObserversEnumerateEnd
    });
}

- (void)signalServiceConnectSuccess:(CIMSignalService *)signalService {
    dispatch_async(imservice_queue, ^{
        self.manualDisconnect = NO;
        self.reconnectionCount = 0;

        ServiceObserversEnumerateBegin
        if ([observer respondsToSelector:@selector(serviceConnectSuccess:)]) {
            [observer serviceConnectSuccess:self];
        }
        ServiceObserversEnumerateEnd
    });
}

- (void)signalServiceDidDisconnect:(CIMSignalService *)signalService error:(NSError *)error {
    dispatch_async(imservice_queue, ^{
        if (!self.manualDisconnect && self.reconnection && self.reconnectionCount < self.reconnectionMaxCount) {
            //如果非手动断开连接 并且 需要自动重连 并且 reconnectionCount < reconnectionMaxCount
            [self startReconnectionTimer];
        } else {
            ServiceObserversEnumerateBegin
            if ([observer respondsToSelector:@selector(serviceDidDisconnect:error:)]) {
                [observer serviceDidDisconnect:self error:error];
            }
            ServiceObserversEnumerateEnd
        }
    });
}

- (void)signalService:(CIMSignalService *)signalService receivePacket:(CIMSignalPacket *)packet {
    dispatch_async(imservice_queue, ^{
        switch (packet.tag) {
            case CIMSignalPacketTypePing:{
                NSLog(@"receive Ping");
                CIMSignalPacket *pongSignalPacket = [self createPongPacket];
                [self.signalService sendSignalPacket:pongSignalPacket];
                break;
            }

            case CIMSignalPacketTypeMessage:{
                NSLog(@"receive Message");
                NSError *error;

                MessageModel *messgae = [[MessageModel alloc] initWithData:packet.packetBody error:&error];

                if (!error) {
                    //返回消息
                    CIMMessage *messageModel = [[CIMMessage alloc] init];
                    messageModel.id_p = messgae.id_p;
                    messageModel.title = messgae.title;
                    messageModel.action = messgae.action;
                    messageModel.timestamp = messgae.timestamp;
                    messageModel.extra = messgae.extra;
                    messageModel.format = messgae.format;
                    messageModel.sender = messgae.sender;
                    messageModel.content = messgae.content;
                    messageModel.receiver = messgae.receiver;

                    ServiceObserversEnumerateBegin
                    if ([observer respondsToSelector:@selector(service:receiveMessage:)]) {
                        [observer service:self receiveMessage:messageModel];
                    }
                    ServiceObserversEnumerateEnd
                } else {
                    ServiceObserversEnumerateBegin
                    if ([observer respondsToSelector:@selector(service:unableParseData:)]) {
                        [observer service:self unableParseData:packet.packetBody];
                    }
                    ServiceObserversEnumerateEnd
                }

                break;
            }

            case CIMSignalPacketTypeReplyBody:{
                NSLog(@"receive ReplyBody");
                NSError *error;
                ReplyBodyModel *replyBodyModel = [[ReplyBodyModel alloc] initWithData:packet.packetBody error:&error];

                if (!error) {
                    CIMReplyBody *replyBody = [[CIMReplyBody alloc] init];
                    replyBody.key = replyBodyModel.key;
                    replyBody.code = replyBodyModel.code;
                    replyBody.message = replyBodyModel.message;
                    replyBody.timestamp = replyBodyModel.timestamp;
                    replyBody.data = replyBodyModel.data_p;

                    ServiceObserversEnumerateBegin
                    if ([observer respondsToSelector:@selector(service:receiveReplyBody:)]) {
                        [observer service:self receiveReplyBody:replyBody];
                    }
                    ServiceObserversEnumerateEnd
                } else {
                    ServiceObserversEnumerateBegin
                    if ([observer respondsToSelector:@selector(service:unableParseData:)]) {
                        [observer service:self unableParseData:packet.packetBody];
                    }
                    ServiceObserversEnumerateEnd
                }

                break;
            }

            default:{
                break;
            }
        }
    });
}

#pragma mark -

#pragma mark - reconnectionTimer

- (void)startReconnectionTimer {
    [self stopReconnectionTimer];
    self.reconnectionTimer = [NSTimer timerWithTimeInterval:self.reconnectionTimeInterval target:[CIMWeakProxy proxyWithTarget:self] selector:@selector(reconnectionTimerHandler) userInfo:nil repeats:NO];
    [NSRunLoop.mainRunLoop addTimer:self.reconnectionTimer forMode:NSRunLoopCommonModes];
}

- (void)stopReconnectionTimer {
    if (self.reconnectionTimer) {
        [self.reconnectionTimer invalidate];
        self.reconnectionTimer = nil;
    }
}

- (void)reconnectionTimerHandler {
    dispatch_async(imservice_queue, ^{
        self.reconnectionCount += 1;

        ServiceObserversEnumerateBegin
        if ([observer respondsToSelector:@selector(service:didReconnection:)]) {
            [observer service:self didReconnection:self.reconnectionCount];
        }
        ServiceObserversEnumerateEnd

        [self connection];
    });
}

#pragma mark -

- (CIMSignalPacket *)createClientBindPacketWithUserId:(NSString *)userId {
    SentBodyModel *clientBindBody = [[SentBodyModel alloc] init];

    clientBindBody.key = @"client_bind";
    clientBindBody.timestamp = (int64_t)[NSDate timeIntervalSinceReferenceDate] * 1000;

    NSMutableDictionary<NSString *, NSString *> *data = [[NSMutableDictionary alloc] init];
    [data setValue:userId forKey:@"uid"];

    NSString *deviceId = [self getDeviceId];
    [data setValue:deviceId forKey:@"deviceId"];

    [data setValue:@"ios" forKey:@"channel"];
    [data setValue:[[UIDevice currentDevice] name] forKey:@"deviceName"];

    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    [data setValue:[infoDictionary objectForKey:@"CFBundleShortVersionString"] forKey:@"appVersion"];
    [data setValue:[[UIDevice currentDevice] systemVersion] forKey:@"osVersion"];

    NSArray<NSString *> *languages = [NSLocale preferredLanguages];
    [data setValue:[languages firstObject] forKey:@"language"];

    clientBindBody.data_p = data;

    NSData *bodyData = clientBindBody.data;

    CIMSignalPacket *clientBindPacket = [[CIMSignalPacket alloc] init];
    clientBindPacket.tag = CIMSignalPacketTypeSentBody;
    clientBindPacket.bodyLength = bodyData.length;
    clientBindPacket.packetBody = bodyData;

    return clientBindPacket;
}

- (CIMSignalPacket *)createPongPacket {
    CIMSignalPacket *pongPacket = [[CIMSignalPacket alloc] init];

    pongPacket.tag = CIMSignalPacketTypePong;

    NSString *pong = @"PONG";
    const char *pongChar = [pong cStringUsingEncoding:NSASCIIStringEncoding];
    UInt16 lenght = pong.length;
    NSData *bodyData = [NSData dataWithBytes:pongChar length:lenght];

    pongPacket.bodyLength = bodyData.length;
    pongPacket.packetBody = bodyData;
    return pongPacket;
}

- (NSString *)getDeviceId {
    NSString *deviceId = [CIMKeychain getValueForKey:DEVICEID_KEY];

    if (!deviceId || 0 == deviceId.length) {
        deviceId = [[NSUUID UUID] UUIDString];
        [CIMKeychain saveValue:deviceId key:DEVICEID_KEY];
    }

    return deviceId;
}

@end
